package com.huiyeji.retry;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author zhaoduo
 */
@Aspect
@Component
public class RetryableAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(RetryableAspect.class);

    @Around("@annotation(retryable)")
    public Object retryOnException(ProceedingJoinPoint joinPoint, Retryable retryable) throws Throwable {
        int currentRetry = -1;
        int maxRetry = retryable.value();
        long delay = retryable.delay();
        String methodName = null;
        Object result = null;

        do {
            currentRetry++;
            try {
                if (currentRetry > 0){
                    LOGGER.info("Method 【{}】 Retry : 【{}/{}】",methodName,currentRetry,maxRetry);
                }
                result = joinPoint.proceed();
                if (currentRetry > 0) {
                    LOGGER.info("Method 【{}】 retried 【{}】 times successfully, ending retries!", methodName, currentRetry);
                }
                return result;
            } catch (Throwable e) {
                if (!retryable.retryFor().isAssignableFrom(e.getClass())){
                    LOGGER.info("Non-specified exception class, giving up on retry");
                    throw e;
                }
                if (currentRetry != 0){
                    LOGGER.info("Retry Error !");
                }
                e.printStackTrace();
                if (currentRetry == 0){
                    MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                    maxRetry = getMaxRetry(joinPoint,methodSignature,retryable);
                    methodName = methodSignature.getDeclaringType().getSimpleName() + "." + methodSignature.getMethod().getName() + "()";
                }
                if (currentRetry >= maxRetry) {
                    LOGGER.info("Method 【{}】 retried 【{}】 times but still threw an exception, ending retries !",methodName,maxRetry);
                    throw e;
                }
                Thread.sleep(delay);
            }
        } while (currentRetry < maxRetry);

        return result;
    }

    private int getMaxRetry(ProceedingJoinPoint joinPoint, MethodSignature methodSignature, Retryable retryable) {
        if (!"".equals(retryable.paramName())){
            Object[] args = joinPoint.getArgs();
            String[] parameterNames = methodSignature.getParameterNames();
            for (int i = 0; i < args.length; i++) {
                if (parameterNames[i].equals(retryable.paramName())){
                    return (Integer) args[i];
                }
            }
        }
        return retryable.value();
    }
}
